﻿using FlowSequencer.Core.FlowElements;
using FlowSequencer.Core.Sequencer.Helper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FlowSequencer.Core.Sequencer
{
    public class CyclicExecutiveScheduling : Sequencer
    {
        private class CycleData
        {
            public int Id { get; set; }
            public int Period { get; set; }
            public int RelativeDeadline { get; set; }
            public int ProcessingTime { get; set; }
            public int ProcessedTime { get; set; }
        }

        int minorCycle;
        int majorCycle;

        List<CycleData> inputData;
        Flow optimalFlow;
        public CyclicExecutiveScheduling() 
        {
            optimalFlow = new Flow();
            optimalFlow.Lines.Add(new Line());
            optimalFlow.Lines[0].Name = "OptimalSequence";
        }

        private static readonly int periodId = 0;
        private static readonly int processingTimeId = 1;
        private static readonly int relativeDeadlineId = 2;


        public override void ComputeFlow()
        {
            //var periodList = inputFlow.Lines[period].Tasks;
            //var processingTimeList = inputFlow.Lines[processingTime].Tasks;
            //var relativeDeadlineList = inputFlow.Lines[relativeDeadline].Tasks;
            inputData = (from period in inputFlow.Lines[periodId].Tasks
                        join processingTime in inputFlow.Lines[processingTimeId].Tasks on period.Index equals processingTime.Index
                        join relativeDeadline in inputFlow.Lines[relativeDeadlineId].Tasks on period.Index equals relativeDeadline.Index
                        select new CycleData { Id = period.Index, Period = period.Cycles, ProcessingTime = processingTime.Cycles, RelativeDeadline = relativeDeadline.Cycles }).ToList<CycleData>();
           
            ComputeMinorAndMajorCycles(inputFlow.Lines[periodId].Tasks);
            for (int cycle = minorCycle; cycle <= majorCycle; cycle += minorCycle)
            {
              //  var readyTasks = inputData.Where(x => x.RelativeDeadline <= cycle).ToList<CycleData>();
                inputData.OrderBy(x => x.RelativeDeadline);
                FillBlock(inputData);
                ResetPeriodLimit(cycle);
            }
            

        }

        private void ResetPeriodLimit(int cycle)
        {
            foreach (var task in inputData)
            {
                var taskMinorCycle = task.Period / minorCycle;
                var currentMinorCycle = cycle / minorCycle;
                if (currentMinorCycle % taskMinorCycle == 0)
                {
                    task.ProcessedTime = 0;
                }
            }
        }

        private bool IsExtendedProcessingLimit(CycleData data)
        {
            return data.ProcessingTime - data.ProcessedTime <= 0;
        }
        private void FillBlock(List<CycleData> readyTasks)
        {
            int units = 0;
          
            int cProcess = 0;
            while (units != minorCycle)
            {
                var task = readyTasks.Skip(cProcess).FirstOrDefault();
                if (task == null) 
                    break;
                if (IsExtendedProcessingLimit(task))
                {
                    cProcess++;
                    continue;
                }
                var availableTime = task.Period > task.ProcessingTime ? task.ProcessingTime : task.Period;
                

                if (availableTime > minorCycle - units)
                {
                    availableTime = minorCycle - units;
                    task.ProcessedTime += availableTime;
                    units = minorCycle;
                }
                else
                {
                    task.ProcessedTime += availableTime;
                    units += availableTime;
                }

                optimalFlow.Lines[0].Tasks.Add(new Task { Index = task.Id, Cycles = availableTime, Name = "Z" + (task.Id) });
                cProcess++;
            }
        }

    

        private void ComputeMinorAndMajorCycles(List<Task> periodList)
        {
            var periods = periodList.Select(x => x.Cycles).ToList();
            minorCycle = MathHelper.Nwd(periods);
            majorCycle = periods.Max();

            optimalFlow.MinorCycle = minorCycle;
            optimalFlow.MajorCycle = majorCycle;
        }

        public override Flow GetOptimizedFlow()
        {
            return optimalFlow;
        }
    }
}
